package com.gw.crm.utils;

import org.apache.commons.lang3.RandomStringUtils;
import org.opencv.calib3d.Calib3d;
import org.opencv.core.*;
import org.opencv.features2d.BFMatcher;
import org.opencv.features2d.ORB;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

import java.util.ArrayList;
import java.util.List;

import static org.opencv.core.Core.findNonZero;
import static org.opencv.imgproc.Imgproc.*;

/**
 * opencv工具类
 * 使用需知：1.本地需要安装opencv；2.把opencv_java4100.dll(linux是.so后缀)文件复制到java的bin目录
 * 版本：opencv4.10.0
 */
public class OpenCVImgUtils {
    static {
        // 加载动态库
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }
    public static void main(String[] args) {
        String uuid = RandomStringUtils.randomAlphanumeric(16);

        String savePath = "D:/uploadFile/uploadPath/merge";

        // 上图
        String up = savePath + "/sx-ding3.png";

        // 下图
        String down = savePath + "/sx-di3.png";

        String saveName = savePath + "/sx-"+uuid+".png";

        // 垂直合并2张图：拼接
        String result = vconcatMergeImg(up,down,saveName);

        System.out.println(result);
    }


    /**
     * 垂直拼接：把2张图合并为一张，去重重叠部分（即物体高度太大在镜头外，一张照片拍不全，顶图的下边部分和底图的上面部分有重叠）
     * @param upImgUrl 绝对路径
     * @param downImgUrl 绝对路径
     * @result 返回文件绝对路径
     */
    public static String vconcatMergeImg(String upImgUrl,String downImgUrl,String saveName){

        // 加载需要拼接的图片（至少2张）
        Mat upMat = Imgcodecs.imread(upImgUrl);
        Mat downMat = Imgcodecs.imread(downImgUrl);

        if(upMat.empty() || downMat.empty()){
            return "合并失败，原因：图片加载失败，请检查文件路径";
        }

        // 检查图片尺寸并调整（确保可以拼接）
        if (upMat.cols() != downMat.cols()) {
            // 调整第二张图片的高度以匹配第一张
            Size newSize = new Size(upMat.cols(), downMat.rows());
            Mat resizedImg2 = new Mat();
            resize(downMat, resizedImg2, newSize);
            downMat = resizedImg2;
        }


        return vconcat2Img(upMat,downMat,saveName);
    }
    /**
     * 垂直拼接：2张图合并为一张，取中间重叠部分（即目标物高度太小在镜头内，顶图的上边部分和底图的上面部分有重叠）
     * @param upImgUrl 绝对路径
     * @param downImgUrl 绝对路径
     * @result 返回文件绝对路径
     */
    public static String vconcatMergeImg2(String upImgUrl,String downImgUrl,String saveName){

        // 加载需要拼接的图片（至少2张）：底为上面，顶为下面
        Mat upMat = Imgcodecs.imread(downImgUrl);
        Mat downMat = Imgcodecs.imread(upImgUrl);

        if(upMat.empty() || downMat.empty()){
            return "合并失败，原因：图片加载失败，请检查文件路径";
        }

        // 检查图片尺寸并调整（确保可以拼接）
        if (upMat.cols() != downMat.cols()) {
            // 调整第二张图片的高度以匹配第一张
            Size newSize = new Size(upMat.cols(), downMat.rows());
            Mat resizedImg2 = new Mat();
            resize(downMat, resizedImg2, newSize);
            downMat = resizedImg2;
        }


        return vconcat2Img2(upMat,downMat,saveName);
    }
    /**
     * 垂直拼接：复杂拼接，按照重叠区域拼接
     * opencv版本4.10.0
     * @param upMat    下图
     * @param downMat       上图
     */
    private static String vconcat2Img(Mat upMat,Mat downMat,String saveName){
        try{
//            System.out.println("1");

            // 1. 将图片转换为灰度图
            Mat grayImg1 = new Mat();
            Mat grayImg2 = new Mat();
            Imgproc.cvtColor(downMat,grayImg1,Imgproc.COLOR_BGR2GRAY);
            Imgproc.cvtColor(upMat,grayImg2,Imgproc.COLOR_BGR2GRAY);

            // 2. 特征提取与匹配
//            System.out.println("2");
            ORB orb = ORB.create();
            MatOfKeyPoint keyPoint1 = new MatOfKeyPoint();
            MatOfKeyPoint keyPoint2 = new MatOfKeyPoint();

            Mat desr1 = new Mat();
            Mat desr2 = new Mat();
            orb.detect(grayImg1,keyPoint1);
            orb.detect(grayImg2,keyPoint2);
            orb.compute(grayImg1,keyPoint1,desr1);
            orb.compute(grayImg2,keyPoint2,desr2);

            // 3. 计算单应性矩阵
//            System.out.println("3");
            MatOfDMatch matches = new MatOfDMatch();
            BFMatcher matcher = BFMatcher.create();
            matcher.match(desr1,desr2,matches);
            List<DMatch> goodMatches = matches.toList();
            if(goodMatches.size() ==0){
                return "合并失败，原因：未匹配到特征点";
            }
            List<Point> points1 = new ArrayList<>();
            List<Point> points2 = new ArrayList<>();

            for (DMatch match : goodMatches){
                points1.add(keyPoint1.toList().get(match.queryIdx).pt);
                points2.add(keyPoint2.toList().get(match.trainIdx).pt);
            }

            // 计算单应性矩阵
            Mat homography = Calib3d.findHomography(new MatOfPoint2f(points1.toArray(new Point[0])),
                    new MatOfPoint2f(points2.toArray(new Point[0])),Calib3d.RANSAC);

            if(homography.empty()){
                return "合并失败，原因：计算单应性矩阵异常";
            }

            // 4.图像拼接
//            System.out.println("4");
            Mat warp = new Mat();
            // 使用单应性矩阵对图片进行透视变换
            Imgproc.warpPerspective(downMat,warp,homography,new Size(downMat.cols(),downMat.rows() + upMat.rows()));
            Imgcodecs.imwrite("D:/uploadFile/uploadPath/merge/sx-warp.png",warp);

            Mat roi = new Mat(warp, new Rect(0, 0, upMat.cols(), upMat.rows()));
            Imgcodecs.imwrite("D:/uploadFile/uploadPath/merge/sx-roi.png",roi);
            Imgcodecs.imwrite("D:/uploadFile/uploadPath/merge/sx-roi-warp.png",warp);

            Mat gray = new Mat();
            Imgproc.cvtColor(roi,gray,Imgproc.COLOR_BGR2GRAY);
            Mat blackMask = new Mat();
            threshold(gray,blackMask,1,255,THRESH_BINARY);
            Mat points = new Mat();
            findNonZero(blackMask,points);
            Rect box = boundingRect(points);

            // x:0y:376 w:1080 h:1493
            System.out.println("x:"+box.x + "y:"+box.y + " w:"+box.width + " h:"+box.height);

            // 两种方法融合，即：上下拼接和中间截取两种处理方法在这里做判断处理
            // roi上面是黑色的(y>0)，则方法正确，否则调用另外一种方法。
            if(box.y >0){
                upMat.copyTo(roi);
                Imgcodecs.imwrite("D:/uploadFile/uploadPath/merge/sx-copy-warp.png",warp);
                Imgcodecs.imwrite("D:/uploadFile/uploadPath/merge/sx-copy-roi.png",roi);

                Mat result = warp;
                Imgcodecs.imwrite("D:/uploadFile/uploadPath/merge/sx-result.png",result);
                // 图片裁切：裁切底部黑色部分，只保留有效像素
                Mat crop = cropImg(result);
                Imgcodecs.imwrite(saveName,crop);
            }else{
                // 图片裁切：裁切顶部黑色部分，只保留有效像素
                Mat crop = new Mat(roi,box);
                Imgcodecs.imwrite(saveName,crop);
            }

            return saveName;
        } catch (CvException ce){
            ce.printStackTrace();
            return "合并失败，异常信息："+ce.getMessage();
        } catch (Exception e){
            e.printStackTrace();
            return "合并失败，异常信息："+e.getMessage();
        }
    }
    /**
     * 垂直拼接：复杂拼接，取重叠区域
     * opencv版本4.10.0
     * @param upMat    下图
     * @param downMat       上图
     */
    private static String vconcat2Img2(Mat upMat,Mat downMat,String saveName){
        try{
//            System.out.println("1");

            // 1. 将图片转换为灰度图
            Mat grayImg1 = new Mat();
            Mat grayImg2 = new Mat();
            Imgproc.cvtColor(downMat,grayImg1,Imgproc.COLOR_BGR2GRAY);
            Imgproc.cvtColor(upMat,grayImg2,Imgproc.COLOR_BGR2GRAY);

            // 2. 特征提取与匹配
//            System.out.println("2");
            ORB orb = ORB.create();
            MatOfKeyPoint keyPoint1 = new MatOfKeyPoint();
            MatOfKeyPoint keyPoint2 = new MatOfKeyPoint();

            Mat desr1 = new Mat();
            Mat desr2 = new Mat();
            orb.detect(grayImg1,keyPoint1);
            orb.detect(grayImg2,keyPoint2);
            orb.compute(grayImg1,keyPoint1,desr1);
            orb.compute(grayImg2,keyPoint2,desr2);

            // 3. 计算单应性矩阵
//            System.out.println("3");
            MatOfDMatch matches = new MatOfDMatch();
            BFMatcher matcher = BFMatcher.create();
            matcher.match(desr1,desr2,matches);
            List<DMatch> goodMatches = matches.toList();
            if(goodMatches.size() ==0){
                return "合并失败，原因：未匹配到特征点";
            }
            List<Point> points1 = new ArrayList<>();
            List<Point> points2 = new ArrayList<>();

            for (DMatch match : goodMatches){
                points1.add(keyPoint1.toList().get(match.queryIdx).pt);
                points2.add(keyPoint2.toList().get(match.trainIdx).pt);
            }

            // 计算单应性矩阵
            Mat homography = Calib3d.findHomography(new MatOfPoint2f(points1.toArray(new Point[0])),
                    new MatOfPoint2f(points2.toArray(new Point[0])),Calib3d.RANSAC);

            if(homography.empty()){
                return "合并失败，原因：计算单应性矩阵异常";
            }

            // 4.图像拼接
//            System.out.println("4");
            Mat warp = new Mat();
            // 使用单应性矩阵对图片进行透视变换
            Imgproc.warpPerspective(downMat,warp,homography,new Size(downMat.cols(),downMat.rows() + upMat.rows()));
            Imgcodecs.imwrite("D:/uploadFile/uploadPath/merge/zj-warp.png",warp);

            Mat roi = new Mat(warp, new Rect(0, 0, upMat.cols(), upMat.rows()));
            // 这里的roi是2个图片重合的地方
            Imgcodecs.imwrite("D:/uploadFile/uploadPath/merge/zj-roi.png",roi);
            Imgcodecs.imwrite("D:/uploadFile/uploadPath/merge/zj-roi-warp.png",warp);

            // TODO 判断黑色区域的位置，如果roi上面是黑色的，则方法正确，否则调用另外一种方法。

            // 图片裁切：裁切顶部黑色部分，只保留有效像素
            Mat crop = cropImg(roi);
//            String uuid = RandomStringUtils.randomAlphanumeric(16);
//            String saveName = savePath + "/"+uuid+".jpg";
            Imgcodecs.imwrite(saveName,crop);

            return saveName;
        } catch (CvException ce){
            ce.printStackTrace();
            return "合并失败，异常信息："+ce.getMessage();
        } catch (Exception e){
            e.printStackTrace();
            return "合并失败，异常信息："+e.getMessage();
        }
    }
    /**
     * 裁切图片：裁切图片中黑色的部分
     * @param img1 图片矩阵
     */
    private static Mat cropImg(Mat img1){
        // 图片灰度信息
        Mat gray = new Mat();
        Imgproc.cvtColor(img1,gray,Imgproc.COLOR_BGR2GRAY);
        Mat blackMask = new Mat();
        threshold(gray,blackMask,1,255,THRESH_BINARY);
        Mat points = new Mat();
        findNonZero(blackMask,points);
        Rect box = boundingRect(points);

        // 裁切有效区域
        Mat crop = new Mat(img1,box);
        return crop;
    }

    /**
     * 水平拼接：普通拼接，直接把2张图水平合并为一张
     * @param leftImgUrl 绝对路径
     * @param rightImgUrl 绝对路径
     */
    public static String hconcatMergeImg(String leftImgUrl,String rightImgUrl,String savePath){
        // 加载需要拼接的图片（至少2张）
        Mat rightMat = Imgcodecs.imread(rightImgUrl);
        Mat leftMat = Imgcodecs.imread(leftImgUrl);

        if(rightMat.empty() || leftMat.empty()){
            System.out.println("图片加载失败，请检查文件路径");
            return "合并异常：图片加载失败，请检查文件路径";
        }

        List<Mat> list = new ArrayList<>();
        list.add(rightMat);
        list.add(leftMat);
        // 水平拼接示例
        Mat horizontalResult = new Mat();
        Core.hconcat(list, horizontalResult);
        String uuid = RandomStringUtils.randomAlphanumeric(16);
        String saveName = savePath + "/"+uuid+".jpg";
        Imgcodecs.imwrite(saveName,horizontalResult);

        return saveName;
    }

}
